/*
 * @Author: rock
 * @Date: 2025-04-20 09:35:06
 * @LastEditors: rock 
 * @LastEditTime: 2025-04-20 21:50:41
 * @FilePath: /011multidownload/main/main.cpp
 * @Description:
 *
 * Copyright (c) 2025 by rock, All Rights Reserved.
 */

#include <iostream>
#include <string>
#include <string.h>
#include <fstream>
#include <sys/mman.h>
#include <fcntl.h>
#include <unistd.h>
#include <cstdio>
#include <sys/mman.h>
// 使用libcurl库下载http请求
#include <curl/curl.h>

typedef struct fileInfo
{
   /* data */
   char* fileptr;//
   int offset;//
}FileInfo;


/**
 * @Author: rock
 * @description:
 *    curl_easy_setopt(curl, CURLOPT_URL, url)的回调, 用做文件下载后的处理
 *    函数将在libcurl接收到数据后被调用
 *    这个回调将在文件下载时一直被循环调用
 * @param :[ void* ptr ]->由char* fileptr = (char*)mmap(NULL, downloadfileLength, PROT_READ|PROT_WRITE, MAP_SHARED, localfileFd, 0);传递, ptr即writeFunc
 * @param :[ size_t size ]->
 * @param :[ size_t memb ]->
 * @param :[ void* userdata ]->由curl_easy_setopt(curl, CURLOPT_WRITEDATA, fileptr)传递, userdata就是fileptr
 * @return :[ ]
 */
size_t writeFunc(void *ptr, size_t size, size_t memb, void *userdata)
{
   //std::cout << "writeFunc : " <<size*memb << "   ";

   FileInfo* fileptr = (FileInfo*)userdata;//FileInfo
   memcpy(fileptr->fileptr+fileptr->offset, ptr, size*memb);
   fileptr->offset += size*memb;//改变偏移量
   return size*memb;
}

/**
 * @Author: rock
 * @description: 获取文件的时候我们要提前获取到文件的大小
 *               并在当前物理磁盘上申请这样大小的空间以便存储下载的文件
 *               这里有两个操作
 *                   1. 获取url文件大小
 *                   2. 在当前物理磁盘上创建一个缓冲区, 用来存放即将下载的文件
 * @param :[ char* url ]-> url源
 * @return :[ long ] -> 返回下载的文件大小, 失败返回-1
 */
double getDownloadFileLength(const char* url)
{
   double downloadFileLength = 0;
   CURL *curl = curl_easy_init();
   curl_easy_setopt(curl, CURLOPT_URL, url);
   curl_easy_setopt(curl, CURLOPT_HEADER, 1);//请求头部
   curl_easy_setopt(curl, CURLOPT_NOBODY, 1);//不需要请求体
   CURLcode res = curl_easy_perform(curl);//执行
   if (CURLE_OK == res)
   {  
      //成功
      //获取下载文件的长度
      curl_easy_getinfo(curl, CURLINFO_CONTENT_LENGTH_DOWNLOAD, &downloadFileLength);
            
   }
   else
   {  
      //失败
      std::cout << "---------------------------Error! getDownloadFileLength : res = " << res << std::endl;
      downloadFileLength = -1;
   }
   //返回
   return downloadFileLength;
}

/**
 * @Author: rock
 * @description: 
 *               1.在本地提前申请需要下载文件的大小内存
 *               2.获取下载的文件
 *               文件下载, 使用libcurl库进行下载操作
 *               获取文件的时候我们要提前获取到文件的大小
 *               并在当前物理磁盘上申请这样大小的空间以便存储下载的文件
 * @param :[ string url ]-> 文件下载地址, 下载链接
 * @param :[ string filename ]-> 文件下载到本地的路径, 保存路径
 * @return :[ ]
 */
// 下载链接
// https://game.chizhoushengnuo.cn/smallresources/2025041816/notepad_113101.exe
// https://pcpackagebssdlbig.kugou.com/202504201022/89a57d0748ed7e7beab7dc67e93901ff/web/kugou_release_20041.exe
//
int download(const char *url, const char *filename)
{

   // 1.在本地提前申请需要下载文件的大小内存
   // std::ofstream localfile(filename, std::ios::out | std::ios::binary);
   int localfileFd = open(filename, O_RDWR|O_CREAT, S_IRUSR|S_IWUSR);//1. 读写方式打开文件, 没有就创建, 2. 给文件添加用户的读写权限
   if(-1 == localfileFd)
   {
      std::cout << "无法创建文件" << std::endl;
   }
   //获取要下载的文件的大小
   long downloadfileLength = getDownloadFileLength(url);
   //先测试好这个downloadfileLength, 因为我们需要我们自己磁盘上申请一个这么大的数据
   std::cout << "downloadfileLength = " << downloadfileLength << std::endl;

   //2.在磁盘创建一个缓冲区来存放文件
   if(-1 == lseek(localfileFd, downloadfileLength-1, SEEK_SET))
   {
      //强制物理磁盘开辟空间
      perror("lseek");
      close(localfileFd);
      return -1;
   }
   if(1 != write(localfileFd, "", 1))
   {
      perror("write");
      close(localfileFd);
      return -1;
   }

   // char* fileptr = (char*)mmap(NULL, downloadfileLength, PROT_READ|PROT_WRITE, localfileFd, 0);
   char* fileptr = (char*)mmap(NULL, downloadfileLength, PROT_READ|PROT_WRITE, MAP_SHARED, localfileFd, 0);
   if(fileptr == MAP_FAILED)
   {
      perror("mmap");
      close(localfileFd);
      return -1;
   }

   FileInfo *fileinfo = (FileInfo*)malloc(sizeof(FileInfo));
   if(fileinfo == NULL)
   {
      munmap(fileptr, downloadfileLength);
      close(localfileFd);
      perror("malloc");
      return -1;
   }
   fileinfo->fileptr = fileptr;
   fileinfo->offset = 0;


   



   // curl_easy_init用来初始化一个CURL的指针, 会返回一个easy_handle(CURL*对象)
   CURL *curl = curl_easy_init();

   // 设置curl
   // 告诉curl库.程序将有的行为
   /**
    * curl : CURL类型的指针
    * CURLOPT_URL : CURLoption类型的选项, 这里表示使用设置访问URL
    * url : 要访问的url
    * **/
   curl_easy_setopt(curl, CURLOPT_URL, url);

   // 设置curl
   /**
    * curl : CURL类型的指针
    * CURLOPT_WRITEFUNCTION : 做数据保存的功能, 调用writeFunc处理下载文件
    * writeFunc : 回调, 处理下载文件
    * **/
   curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writeFunc);
   curl_easy_setopt(curl, CURLOPT_WRITEDATA, fileinfo);

   // 执行, 下载文件, 耗时操作
   // 建立tcp连接, 发送http请求, 返回值res为返回的状态, 成功返回CURLE_OK
   CURLcode res = curl_easy_perform(curl);//对多线程并不是很友善
   if (CURLE_OK != res)
   {
      std::cout << "Error! download : res = " << res << std::endl;
   }

   // 结束会话, 配合curl_easy_init
   curl_easy_cleanup(curl);

   free(fileinfo);
   munmap(fileptr, downloadfileLength);
   close(localfileFd);

   return 1;
}


void func01()
{
   std::cout << "Hello" << std::endl;

   // 用argv来传递参数
   // download(argv[1], argv[2]);

   const char* url = "https://game.chizhoushengnuo.cn/smallresources/2025041816/notepad_113101.exe";
   const char* filename = R"(kugou.exe)";
   download(url, filename);


}

int main(int argc, const char *argv[])
{
   func01();
   return 0;
}
